// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GitHubVulnerabilities2v3.Configuration;
using GitHubVulnerabilities2v3.Extensions;
using Moq;
using Xunit;
using NuGet.Services.Storage;
using NuGet.Services.Cursor;
using GitHubVulnerabilities2v3.Telemetry;
using Microsoft.Extensions.Logging;
using NuGet.Services.Entities;
using GitHubVulnerabilities2v3.Entities;

namespace GitHubVulnerabilities2v3.Facts
{
    public class BlobStorageVulnerabilityWriterFacts
    {
        private readonly GitHubVulnerabilities2v3Configuration _configuration;
        private readonly Mock<IStorageFactory> _storageFactoryMock;
        private readonly Mock<ILogger<BlobStorageVulnerabilityWriter>> _loggerMock;
        private readonly Mock<ILogger<Storage>> _storageLoggerMock;
        private readonly Mock<ITelemetryService> _telemetryServiceMock;
        private Mock<IStorage> _storage;

        public BlobStorageVulnerabilityWriterFacts()
        {
            _configuration = new GitHubVulnerabilities2v3Configuration();
            _telemetryServiceMock = new Mock<ITelemetryService>();
            _loggerMock = new Mock<ILogger<BlobStorageVulnerabilityWriter>>();
            _storageLoggerMock = new Mock<ILogger<Storage>>();

            _storage = new Mock<IStorage>();

            _storageFactoryMock = new Mock<IStorageFactory>();
            _storageFactoryMock.Setup(x => x.Create(null))
                .Returns(new StoragePassthrough(_storage.Object, _storageLoggerMock.Object));
        }

        [Fact]
        public async Task RegenerateOnOld()
        {
            // Arrange
            var cursorMock = new Mock<ReadWriteCursor<DateTimeOffset>>();
            cursorMock.SetReturnsDefault<Task>(Task.FromResult(true));
            cursorMock.Setup(x => x.Value)
                .Returns(DateTimeOffset.UtcNow.AddDays(-31));

            var service = new BlobStorageVulnerabilityWriter(
                _storageFactoryMock.Object,
                _configuration,
                cursorMock.Object,
                _loggerMock.Object,
                _telemetryServiceMock.Object
                );

            // Act
            var runMode = await service.DetermineRunMode(cursorMock.Object);

            // Assert
            Assert.True(runMode == RunMode.Regenerate);
        }

        [Fact]
        public async Task UpdateOnNew()
        {
            // Arrange
            var cursorMock = new Mock<ReadWriteCursor<DateTimeOffset>>();
            cursorMock.SetReturnsDefault<Task>(Task.FromResult(true));
            cursorMock.Setup(x => x.Value)
                .Returns(DateTimeOffset.UtcNow.AddDays(-15));

            var service = new BlobStorageVulnerabilityWriter(
                _storageFactoryMock.Object,
                _configuration,
                cursorMock.Object,
                _loggerMock.Object,
                _telemetryServiceMock.Object
                );

            // Act
            var runMode = await service.DetermineRunMode(cursorMock.Object);

            // Assert
            Assert.True(runMode == RunMode.Update);
        }

        [Fact]
        public async Task RegenerateOnSpecialCase()
        {
            // Arrange
            var testId = "testId";
            var testUrl = "https://www.testurl.com/12345";
            var cursorMock = new Mock<ReadWriteCursor<DateTimeOffset>>();
            cursorMock.SetReturnsDefault<Task>(Task.FromResult(true));
            cursorMock.Setup(x => x.Value)
                .Returns(DateTimeOffset.UtcNow.AddDays(-15));

            var service = new BlobStorageVulnerabilityWriter(
                _storageFactoryMock.Object,
                _configuration,
                cursorMock.Object,
                _loggerMock.Object,
                _telemetryServiceMock.Object
                );

            PackageVulnerability newVulnerability = new PackageVulnerability();
            newVulnerability.AffectedRanges = 
                new VulnerablePackageVersionRange[]
                {
                    new VulnerablePackageVersionRange
                    {
                        PackageId= testId,
                    }
                };
            
            newVulnerability.AdvisoryUrl = testUrl;
            newVulnerability.Severity = PackageVulnerabilitySeverity.Low;

            var baseContent = new Dictionary<string, List<Advisory>>();
            baseContent.Add(testId, new List<Advisory>());
            baseContent[testId].Add(new Advisory
            {
                Url = testUrl
            });

            // Act
            await service.WriteVulnerabilityAsync(newVulnerability, false);

            // Assert
            Assert.True(await service.ShouldRegenerateForSpecialCase(baseContent));
        }

        private class StoragePassthrough : Storage
        {
            private IStorage _storage;

            public StoragePassthrough(IStorage storage, ILogger<Storage> logger)
                : base(new Uri("https://www.mock.mock"), logger)
            {
                _storage = storage;
            }

            public override bool Exists(string fileName)
            {
                return _storage.Exists(fileName);
            }

            public override Task<bool> ExistsAsync(string fileName, CancellationToken cancellationToken)
            {
                return _storage.ExistsAsync(fileName, cancellationToken);
            }

            public override IEnumerable<StorageListItem> List(bool getMetadata)
            {
                return _storage.List(getMetadata);
            }

            public override Task<IEnumerable<StorageListItem>> ListAsync(bool getMetadata, CancellationToken cancellationToken)
            {
                return _storage.ListAsync(getMetadata, cancellationToken);
            }

            public override Task SetMetadataAsync(Uri resourceUri, IDictionary<string, string> metadata)
            {
                return _storage.SetMetadataAsync(resourceUri, metadata);
            }

            protected override Task OnCopyAsync(Uri sourceUri, IStorage destinationStorage, Uri destinationUri, IReadOnlyDictionary<string, string> destinationProperties, CancellationToken cancellationToken)
            {
                throw new NotImplementedException();
            }

            protected override Task OnDelete(Uri resourceUri, CancellationToken cancellationToken)
            {
                throw new NotImplementedException();
            }

            protected override Task<StorageContent> OnLoad(Uri resourceUri, CancellationToken cancellationToken)
            {
                throw new NotImplementedException();
            }

            protected override Task OnSave(Uri resourceUri, StorageContent content, bool overwrite, CancellationToken cancellationToken)
            {
                throw new NotImplementedException();
            }
        }
    }
}
